package com.jxpanda.plugin.initializr.config

import com.baomidou.mybatisplus.generator.AutoGenerator
import com.baomidou.mybatisplus.generator.config.*
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder
import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert
import com.baomidou.mybatisplus.generator.config.po.TableField
import com.baomidou.mybatisplus.generator.config.querys.MySqlQuery
import com.baomidou.mybatisplus.generator.config.rules.DateType
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType
import com.baomidou.mybatisplus.generator.config.rules.IColumnType
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy
import java.io.File

class AutoGeneratorBuilder private constructor() {

    private lateinit var projectPath: String
    private lateinit var config: Config

    companion object {
        fun builder(): AutoGeneratorBuilder = AutoGeneratorBuilder()
    }

    fun projectPath(projectPath: String): AutoGeneratorBuilder {
        this.projectPath = projectPath
        return this
    }

    fun config(config: Config): AutoGeneratorBuilder {
        this.config = config
        return this
    }

    fun build(): AutoGenerator {
        val packageConfig = packageConfig()
        return AutoGenerator(dataSource())
            .global(globalConfig())
            .packageInfo(packageConfig)
            .strategy(strategyConfig())
            .template(templateConfig())
            .injection(injectionConfig(packageConfig, config.generator.model))
    }

    private fun injectionConfig(packageConfig: PackageConfig, model: Config.Model): InjectionConfig {
        return InjectorConfig(packageConfig, model)
            .addInjector(EnumInjector())
            .addInjector(JsonInjector())
            .addInjector(CustomerConfigInjector())
    }

    private fun globalConfig(): GlobalConfig {
        return GlobalConfig.Builder()
            .disableOpenDir()
            .outputDir("$projectPath${File.separator}src${File.separator}main${File.separator}java")
            .author(System.getProperty("user.name"))
            .dateType(DateType.TIME_PACK)
            .enableSwagger()
            .build()
    }

    private fun dataSource(): DataSourceConfig {
        val dataSource = config.generator.dataSource

        return DataSourceConfig.Builder(dataSource.url, dataSource.username, dataSource.password)
            .dbQuery(object : MySqlQuery() {
                override fun fieldCustom(): Array<String> {
                    return arrayOf("DEFAULT")
                }
            })
            .typeConvert(object : MySqlTypeConvert() {
                override fun processTypeConvert(globalConfig: GlobalConfig, tableField: TableField): IColumnType {
                    return if (tableField.isId()) {
                        DbColumnType.STRING
                    } else if (tableField.isBoolean()) {
                        DbColumnType.BOOLEAN
                    } else {
                        super.processTypeConvert(globalConfig, tableField)
                    }
                }
            })
            .build()
    }


    private fun packageConfig(): PackageConfig {
        return PackageConfig.Builder()
            .parent(config.generator.`package`)
            .build()
    }

    private fun templateConfig(): TemplateConfig {
        return TemplateConfig.Builder()
            .controller("/templates/model/controller.java")
            .entity("/templates/model/entity.java")
            .service("/templates/model/service.java")
            .serviceImpl("/templates/model/serviceImpl.java")
            .mapper("/templates/model/mapper.java")
            .xml("/templates/model/mapper.xml")
            .build()
    }

    private fun strategyConfig(): StrategyConfig {
        val tables = config.generator.tables
        val model = config.generator.model
        val strategyConfig = StrategyConfig.Builder()
            .addTablePrefix(model.prefixes)
            .addInclude(*tables.include.toTypedArray())
            .addExclude(*tables.exclude.toTypedArray())
            .entityBuilder()
            .fileOverride()
            .naming(NamingStrategy.underline_to_camel)
            .superClass(model.superClass.entity)
            .addSuperEntityColumns(model.superEntityColumns)
            .logicDeleteColumnName(model.logicDeleteColumnName)
            .enableColumnConstant()
            .enableLombok()
            .mapperBuilder()
            .fileOverride()
            .enableBaseColumnList()
            .enableBaseResultMap()
            .superClass(model.superClass.mapper)
            .serviceBuilder()
            .fileOverride()
            .convertServiceFileName { entityName: String -> entityName + ConstVal.SERVICE }
            .superServiceClass(model.superClass.service)
            .superServiceImplClass(model.superClass.serviceImpl)
            .controllerBuilder()
            .fileOverride()
            .convertFileName { controllerFilename: String -> controllerFilename + "Controller" }
            .enableRestStyle()
            .build()

        return strategyConfig.entityBuilder()
            .nameConvert(object : INameConvert.DefaultNameConvert(strategyConfig) {
                override fun propertyNameConvert(field: TableField): String {
                    val propertyName = super.propertyNameConvert(field)
                    return if (field.isBoolean()) {
                        propertyName.removePrefix("is").replaceFirstChar { it.lowercase() }
                    } else {
                        propertyName
                    }
                }
            })
            .build()
    }

    private fun TableField.isId() = this.columnName.endsWith("_id") && this.type.contains("bigint")
    private fun TableField.isBoolean() = this.columnName.startsWith("is_") && this.type.contains("tinyint")


}